/* Aritmetico es la clase que nuclea la logica de comportamiento de un compresor
 * de tipo Aritmetico dinamico de orden uno. El Aritmetico basicamente recibe una tira de bytes que transforma en otra tira,
 * en lo posible de menor tamaño. Eso sera posible si los bytes tiene tal forma en que se repiten pares de caracteres a lo largo del mismo.
 * Cada aritmetico tiene una lista de contextos en donde guarda las probabilidades de cada carater dado un caracter anterior.
 * Una misma instancia puede comprimir varias tiras de bytes independientes, creando salidas independientes, y esa misma instancia puede descomprimir
 * todas las salidas en cuanquier orden (pudiendo descomprimir primero la ultima comprimida). Ademas no es necesario que la misma instancia sea quien
 * comprima y descomprima.
 * */

#include <list>
#include "Simbolo.h"
#include "Contexto.h"
#include "../Constantes.h"
#include "../Bytes.h"
#include "Compresor.h"
#ifndef ARITMETICO_H_
#define ARITMETICO_H_

class Aritmetico: public Compresor {
private:

	/*Vector con los contextos que van apareciendo*/
	std::vector<Contexto> contextos;

	/*Puntero al contexto actual*/
	Contexto* contextoActual;

	/*Flag que indica si es la primer compresion/descompresion*/
	bool contextoNulo;

	/*Buffer en donde se gurdan los bits hasta tener un Byte*/
	char buffer;

	/*Cantidad de bits cargados en el buffer*/
	int bitsEnBuff;

	/*Se guarda la salida del la compresion/descompresion*/
	Bytes salida;

	/*contador de undeflow, propio del algoritmo*/
	int contadorUnderflow;

	/*Techo  de la actual compresion/descompresion*/
	uint32 techo;

	/*Piso de la actual compresion/descompresion*/
	uint32 piso;

public:
	/*Constructor */
	Aritmetico();

	/*Destructor*/
	virtual ~Aritmetico();

	uint32 getPiso();

	uint32 getTecho();

	void setPiso(uint32 valor);

	void setTecho(uint32 valor);

	Bytes getSalida();

	/*El aritmetico queda como si recien se hubiese creado.*/
	void reset();

	/*Se coloca el contador de Underflow en cero*/
	void resetContadorUnderflow();

	/*Se agrega padding al final del ultimo byte del tipo 10...0*/
	void agregarPadding();

	/*Se procede a que el puntero de Contexto Actual apunte al contexto ubicado en la posicion posicion del vector de contextos*/
	void actualizarContexto(int posicion);

	/*Se agrega un nuevo contexto a la lista de contextos que tiene el simbolo passado por parametro
	 * pre: se sabe que no existe un contexto con dicho simbolo en el vector de contextos*/
	void agregarContexto(char simbolo);

	/* Se pasa a apuntar al contexto actual que tiene dicho simbolo.
	 * Si no hay ningun contexto con este simbolo en el vector de contextos se lo crea y se lo agrega. */
	void procesarContexto(char simbolo);

	/* Se analizan el techo y el piso en busca de overflow,
	 * Retorna cantidad de bits a leer de s (o cero si no hay overflow),
	 * s contiene los bits que se retiraran a causa del overflow*/
	char overflow(uint32 techo, uint32 piso, uint32 &s);

	/* Se analizan el techo y el piso en busca de underflow,
	 * Retorna cantidad de bits de underflow o cero si no hubo.*/
	char underflow(uint32 techo, uint32 piso);

	/*Agrega un bit al final del buffer, si se llena lo vuelca en la salida*/
	void pushBit(char bit);

	/* Emite los bits de acuerdo a lo que obtuvo de overflow*/
	void emitirOver(char cantBitsOver, uint32 bitsSalida);

	/*Se calculan el piso y el techo de este simbolo  dentro del contexto actual,
	 * y se retornan en los parametros. Puede ser para el caso
	 * de comprimir, donde se busca segun el simbolo;
	 * o de descomprimir, en dónde el valorSimbolo se encuentra dentro del intervalo [piso , techo]*/
	void CalcularPisoTecho(char& simbolo, uint32& pisoSimbolo,
			uint32& techoSimbolo, CompreODescompre idaOVuelta,
			uint32 valorSimbolo);

	/*Se retiran los bits de overflow y se calcula el techo (se agregan unos al final) segun el overflow*/
	void nuevoTechoOver(uint32& techoSimbolo, char cantALeer);

	/*Se retiran los bits de overflow y se calcula el piso (se agregan ceros al final) segun el overflow*/
	void nuevoPisoOver(uint32& pisoSimbolo, char cantALeer);

	/*se invoca a nuevoTechoOver y a nuevoPisoOver*/
	void nuevoTechoPisoOver(uint32& techoSimbolo, uint32& pisoSimbolo,
			char cantALeer);

	/*Se retiran los bits de underflow y calcula el techo (se agregan unos al final) segun el underflow*/
	void nuevoTechoUnder(uint32& techoSimbolo, char cantALeer);

	/*Se retiran los bits de underflow y se calcula el piso (se agregan ceros al final) segun el underflow*/
	void nuevoPisoUnder(uint32& pisoSimbolo, char cantALeer);

	/*se invoca a nuevoTechoUnder y a nuevoPisoUnder*/
	void nuevoTechoPisoUnder(uint32& techoSimbolo, uint32& pisoSimbolo,
			char cantALeer);

	/*Se procesa/comprime uno de los caracteres de la tira de bytes, guardando dicho resultado en la variabl salida del aritmetico*/
	void procesarSimbolo(char simbolo);

	/* Se combrime una tira de bytes y se retorna la tira comprimida.
	 * Internamente se llama a procesar  simbolo, para cada caracter del la tira de bytes y al final se agregan el piso y el padding*/
	Bytes comprimir(Bytes bytes);

	/* La entrada se traduce a su verdadero valor mediantes el proceso de descompresion dinamico y
	 * se retorna*/
	Bytes descomprimir(Bytes entrada);

	/* Si tengo menos de 4 bytes agrego bytes con ceros al final.
	 * Luego cargo 4 bytes a mi ventana de lectura*/
	void inicializarVentana(Bytes& bytes, uint32& ventanaBits);

	/*Retorna los N bits siguientes a la posicion determinada por byteN y bitN
	 * N debe estar entre 1 y 32.
	 * Si se piden 9 bit se retornan 00000000000000000000000|111011010
	 * del resultado, son validos los N bits finales.
	 * */
	uint32 leerNbits(Bytes& bytes, char N, char& bitN, cantBytes& byteN);

	/*La ventana es una vaiable de 32 bits que funciona como una cola, es decir los nuevos bits se agregan
	 * al final y los que sobran se descartan, al mover N bits la ventana, lo que se hace es leer  bits de
	 * una variable Bytes y agregarlos a la ventana, de esta forma se simula una ventana de inspeccion
	 * recorriendo los Bytes*/
	void moverVentanaNbits(uint32& ventanaBits, char N, Bytes &input,
			cantBytes& contadorBytes, char& bitN, casoDesborde tipoDesborde);

	/*Se guarda en la salida el piso completo del contexto actual*/
	void emitirUltimoPiso();

	/*Si hay bytes nulos luego del padding, se retiran los mismos y se trunca la variable bytes*/
	void sacarRelleno(Bytes& bytes);

	/*Si el valorADecodificar es igual al piso del símbolo actual, entonces, se retorna true*/
	bool posibleEmisionFinal(uint32 pisoSimbolo, uint32 valorADecodificar);

	/*Si posibleEmisionFinal dio true, debo ver si lo que sigue es padding, si asi es entonces, debo terminar de leer y
	 * descomprimir: retorno true*/
	bool estoyEnElPadding(char bitN, cantBytes byteN, const Bytes& bytes);

	/*Se imprime el vector de contextos*/
	void imprimirContextos();
};

#endif /* ARITMETICO_H_ */
